package com.itheima.auth.intergration.filter;

import com.itheima.auth.intergration.authenticator.IntegrationAuthenticator;
import com.itheima.auth.intergration.entity.IntegrationAuthenticationEntity;
import com.itheima.auth.intergration.threadlocal.IntegrationAuthenticationHolder;
import com.itheima.common.threadlocal.UserHolder;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.OrRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.stereotype.Component;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.IOException;
import java.util.*;

/**
 * 只处理 /oauth/token POST 密码模式认证请求
 */
@Component
public class IntegrationAuthenticationFilter extends GenericFilter implements ApplicationContextAware {

    private static final String AUTH_TYPE_PARM_NAME = "auth_type";//登录类型参数名

    //目前项目中所有的认证方式实现对象
    private Collection<IntegrationAuthenticator> authenticators;


    //用户判断请求路径，提交方式是否匹配
    private RequestMatcher requestMatcher;

    //在过滤器对象进行实例化时候 增加匹配规则
    public IntegrationAuthenticationFilter() {
        this.requestMatcher = new OrRequestMatcher(
                new AntPathRequestMatcher("/oauth/token", "POST")
        );
    }

    /**
     * 过滤器逻辑
     *
     * @param servletRequest
     * @param servletResponse
     * @param filterChain
     * @throws IOException
     * @throws ServletException
     */
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
        //1.判断当前请求是否需要处理
        if (requestMatcher.matches(httpServletRequest)) {
            //2.如果满足要求说明用户 在做密码模式的认证
            RequestParameterWrapper requestParameterWrapper = new RequestParameterWrapper(httpServletRequest);

            //判断：security框架默认认证 必须要提交参数 password
            if (requestParameterWrapper.getParameter("password") == null){
                //手动增加请求参数password
                requestParameterWrapper.addParameter("password", "");
            }

            //2.1 将用户提交参数接收到 封装认证信息实体对象
            IntegrationAuthenticationEntity entity = new IntegrationAuthenticationEntity();
            entity.setAuthType(requestParameterWrapper.getParameter(AUTH_TYPE_PARM_NAME));
            entity.setAuthParameters(requestParameterWrapper.getParameterMap());

            //2.2 将认证信息存入到ThreadLocal中
            IntegrationAuthenticationHolder.set(entity);

            //3.将请求继续转发-执行认证
            filterChain.doFilter(servletRequest, servletResponse);

            //4.将当次 ThreadLocal中认证信息清除
            IntegrationAuthenticationHolder.remove();
            UserHolder.remove();
        } else {
            //路径没有匹配上，放行
            filterChain.doFilter(servletRequest, servletResponse);
        }
    }

    /**
     * @param applicationContext spring容器对象
     * @throws BeansException
     */
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        //从spring容器中获取自定义认证器实例
        Map<String, IntegrationAuthenticator> map = applicationContext.getBeansOfType(IntegrationAuthenticator.class);
        if (map != null){
            this.authenticators = map.values();
        }
    }


    /**
     * 用途：在拦截时给Request添加参数
     * Cloud OAuth2 密码模式需要判断Request是否存在password参数，
     * 如果不存在会抛异常结束认证
     * 所以在调用doFilter方法前添加password参数
     */
    class RequestParameterWrapper extends HttpServletRequestWrapper {
        private Map<String, String[]> params = new HashMap<String, String[]>();

        public RequestParameterWrapper(HttpServletRequest request) {
            super(request);
            this.params.putAll(request.getParameterMap());
        }

        public RequestParameterWrapper(HttpServletRequest request, Map<String, Object> extraParams) {
            this(request);
            addParameters(extraParams);
        }

        public void addParameters(Map<String, Object> extraParams) {
            for (Map.Entry<String, Object> entry : extraParams.entrySet()) {
                addParameter(entry.getKey(), entry.getValue());
            }
        }

        @Override
        public String getParameter(String name) {
            String[] values = params.get(name);
            if (values == null || values.length == 0) {
                return null;
            }
            return values[0];
        }

        @Override
        public String[] getParameterValues(String name) {
            return params.get(name);
        }

        @Override
        public Map<String, String[]> getParameterMap() {
            return params;
        }

        public void addParameter(String name, Object value) {
            if (value != null) {
                if (value instanceof String[]) {
                    params.put(name, (String[]) value);
                } else if (value instanceof String) {
                    params.put(name, new String[]{(String) value});
                } else {
                    params.put(name, new String[]{String.valueOf(value)});
                }
            }
        }
    }

}
